Skip to content

feat(signals): add SignalStoreFeatureType - #5186

Merged
markostanimirovic merged 12 commits into
mainfrom
signals/feat/signal-store-feature-type
Aug 6, 2026
Merged

feat(signals): add SignalStoreFeatureType#5186
markostanimirovic merged 12 commits into
mainfrom
signals/feat/signal-store-feature-type

Conversation

@rainerhahnekamp

Copy link
Copy Markdown
Contributor

This allows custom features to reuse another feature's inferred state, props, and methods as input without duplicating the feature result type manually.

PR Checklist

Please check if your PR fulfills the following requirements:

PR Type

What kind of change does this PR introduce?

[ ] Bugfix
[x] Feature
[ ] Code style update (formatting, local variables)
[ ] Refactoring (no functional changes, no api changes)
[ ] Build related changes
[ ] CI related changes
[ ] Documentation content changes
[ ] Other... Please describe:

What is the current behavior?

Closes #5185

What is the new behavior?

Does this PR introduce a breaking change?

[ ] Yes
[x] No

@rainerhahnekamp
rainerhahnekamp force-pushed the signals/feat/signal-store-feature-type branch from e557e41 to 99d3ad8 Compare July 1, 2026 22:56
@rainerhahnekamp
rainerhahnekamp marked this pull request as ready for review July 1, 2026 22:57
Comment thread modules/signals/src/signal-store-models.ts Outdated
@martin-the-geek

Copy link
Copy Markdown

We have something similar in our project except we have a second generic parameter where we can specify a subset of keys to depend on which helps limit the setup in our feature tests.

From memory it went something like this

type StoreFeatureResultKeys<T extends StoreFeatureResult> = keyof T['state'] | keyof T['props'] | keyof T['methods'];

type ExtractedStoreFeature<Feature extends (...params: never[]) => unknown> =
  ReturnType<Feature> extends SignalStoreFeature<infer Input, infer Output>
    ? Input & Output
    : never;

export type SignalStoreFeatureType<
  Feature extends (...params: never[]) => unknown,
  Keys extends StoreFeatureResultKeys<ExtractedStoreFeature<Feature>> = StoreFeatureResultKeys<ExtractedStoreFeature<Feature>>
> = {
  state: Pick<ExtractedStoreFeature<Feature>>['state'], Extract<Keys, keyof ExtractedStoreFeature<Feature>>['state']>>,
  props: Pick<ExtractedStoreFeature<Feature>>['props'], Extract<Keys, keyof ExtractedStoreFeature<Feature>>['props']>>,
  methods: Pick<ExtractedStoreFeature<Feature>>['methods'], Extract<Keys, keyof ExtractedStoreFeature<Feature>>['methods']>>,
};

// to depend on an entire feature
type<SignalStoreFeatureType<withSomeFeature>>()

// to depend on a subset of the feature
type<SignalStoreFeatureType<withSomeFeature, 'stateA' | 'methodB'>>()

@rainerhahnekamp

Copy link
Copy Markdown
Contributor Author

@martin-the-geek, but why would you want that?

I see SignalStoreFeatureType is a type for a specific feature. If you want a more generic one that could be implemented by multiple signalStoreFeature types, then I understand the motivation behind your approach. But even then, I would write a generic type manually, meaning I would not like to depend on an existing one.

I just don't know if we are overcomplicating things here.

@martin-the-geek

Copy link
Copy Markdown

@rainerhahnekamp Our motivation for having a convenience type to identify a subset of a feature is to narrow the dependencies of another feature to limit the impacts to our feature tests.

If feature A depends on only a few signals from feature B but feature B has dozens, when testing feature A we only have to add the few bits to our test store that feature A depends on. Also, if in the future feature B adds some elements that are irrelevant to feature A, we don't have to update feature A's tests to include the new elements. Problem gets worse if feature B depends on feature C, etc.

Sure, I guess we just keep the key limiting type custom to our app, but it might be useful for others.

Comment thread modules/signals/spec/types/signal-store-feature-type.spec.ts
Comment thread modules/signals/src/index.ts
Comment thread projects/www/src/app/pages/guide/signals/signal-store/custom-store-features.md Outdated
Comment thread modules/signals/src/signal-store-models.ts
Comment thread modules/signals/src/signal-store-models.ts Outdated
Comment thread projects/www/src/app/pages/guide/signals/signal-store/custom-store-features.md Outdated
Comment thread modules/signals/spec/types/signal-store-feature-type.spec.ts
@rainerhahnekamp
rainerhahnekamp force-pushed the signals/feat/signal-store-feature-type branch from 7786469 to 53a41fd Compare August 5, 2026 18:36
@rainerhahnekamp

Copy link
Copy Markdown
Contributor Author

@markostanimirovic, could please re-check?

@markostanimirovic markostanimirovic left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👏

@markostanimirovic
markostanimirovic merged commit 319d3ee into main Aug 6, 2026
6 checks passed
@markostanimirovic
markostanimirovic deleted the signals/feat/signal-store-feature-type branch August 6, 2026 00:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

@ngrx/signals: type representing a SignalStoreFeature

5 participants